/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.plan.metrix;

import org.mmtk.plan.Phase;
import org.mmtk.plan.StopTheWorld;
import org.mmtk.plan.TransitiveClosure;
import org.mmtk.policy.MarkSweepSpace;
import org.mmtk.policy.Space;
import org.mmtk.utility.Log;
import org.mmtk.utility.heap.VMRequest;
import org.mmtk.utility.options.Options;
import org.mmtk.utility.statistics.Stats;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.ObjectReference;

/**
 * This class implements the global state of a simple mark-sweep collector.
 * 
 * All plans make a clear distinction between <i>global</i> and
 * <i>thread-local</i> activities, and divides global and local state into
 * separate class hierarchies. Global activities must be synchronized, whereas
 * no synchronization is required for thread-local activities. There is a single
 * instance of Plan (or the appropriate sub-class), and a 1:1 mapping of
 * PlanLocal to "kernel threads" (aka CPUs or in Jikes RVM, Processors). Thus
 * instance methods of PlanLocal allow fast, unsychronized access to functions
 * such as allocation and collection.
 * 
 * The global instance defines and manages static resources (such as memory and
 * virtual memory resources). This mapping of threads to instances is crucial to
 * understanding the correctness and performance properties of MMTk plans.
 */
@Uninterruptible
public class Metrix extends StopTheWorld {

	/****************************************************************************
	 * Constants
	 */
	//this is to allow running GC at smaller stress intervals
	private static final int MY_DEFAULT_POLL_FREQUENCY = (128 << 8) >> LOG_BYTES_IN_PAGE;
	
	//these are to set different verbosity levels
	static final boolean PILOT_VERBOSE= true;
	
	//these are to set different flavors of 
	private static final int DEPTH_FIX_DEPTH = 0;
	private static final int TIME_FIX_TIME = 1;
	private static final int TRACE_FLAVOR = DEPTH_FIX_DEPTH;
	
	private static final int CLOSURE_FIRST_CONCURRENT = 0;
	private static final int CLOSURE_LAST_CONCURRENT = 1;
	private static final int CLOSURE_FIRST_SINGLE = 2;
	private static final int CLOSURE_LAST_SINGLE = 3;
	private static final int CLOSURE_CONCURRENT = 4;
	private static final int CLOSURE_SINGLE = 5;
	private static int closure;
	
	static final short concClosure1 = Phase.createSimple("concClosure1");
	static final short concClosure2 = Phase.createSimple("concClosure2");
	static final short depthClosure = Phase.createSimple("depthClosure");
	static final short fixClosure = Phase.createSimple("fixClosure");
	static final short depthCheckClosure = Phase.createSimple("depthCheckClosure");
	static final short prepareClosure = Phase.createSimple("prepareClosure", null);
	private static final short DFD_ClosurePhase = Phase.createComplex(
			"DFD_ClosurePhase", 
			null, 
			Phase.scheduleMutator(PREPARE), 
			Phase.scheduleGlobal(PREPARE), 
			Phase.scheduleCollector(PREPARE),
			Phase.scheduleComplex(prepareStacks), 
			Phase.scheduleCollector(STACK_ROOTS), 
			Phase.scheduleCollector(ROOTS), 
			Phase.scheduleGlobal(ROOTS),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(depthClosure),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(fixClosure),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(depthCheckClosure));
	
	private static final short TFT_ClosurePhase = Phase.createComplex(
			"TFT_ClosurePhase", 
			null, 
			Phase.scheduleMutator(PREPARE), 
			Phase.scheduleGlobal(PREPARE), 
			Phase.scheduleCollector(PREPARE),
			Phase.scheduleComplex(prepareStacks), 
			Phase.scheduleCollector(STACK_ROOTS), 
			Phase.scheduleCollector(ROOTS), 
			Phase.scheduleGlobal(ROOTS),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(concClosure1),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(fixClosure),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(concClosure2));
	/****************************************************************************
	 * Class variables
	 */
	public static final MarkSweepSpace myMsSpace = new MarkSweepSpace("myms",
			MY_DEFAULT_POLL_FREQUENCY, VMRequest.create());
	public static final int MARK_SWEEP = myMsSpace.getDescriptor();

	public static final int SCAN_MARK = 0;

	/****************************************************************************
	 * Instance variables
	 */
	public final MetrixTrace pilotTrace = new MetrixTrace(metaDataSpace);

	public Metrix() {
		//disable reference types scan
		Options.noReferenceTypes.setValue(true);
	    Options.noFinalizer.setValue(true);
	    Options.printPhaseStats.setValue(true);
	}
	
	public final TraceBranch ballancer = new TraceBranch();
	/*****************************************************************************
	 * 
	 * Collection
	 */

	/**
	 * Perform a (global) collection phase.
	 * 
	 * @param phaseId
	 *            Collection phase to execute.
	 */
	@Inline
	public void collectionPhase(short phaseId) {
		if (phaseId == PREPARE) {
			//disable reference types scan
			VM.finalizableProcessor.clear();
		    VM.weakReferences.clear();
		    VM.softReferences.clear();
		    VM.phantomReferences.clear();
		    
			super.collectionPhase(phaseId);
			this.pilotTrace.prepare();
			myMsSpace.prepare(true);
			startClosure();
			return;
		}

		if (phaseId == prepareClosure) {	
			this.pilotTrace.prepare();
			if(!isFirstClosure()) {
				//this will be not a first closure
			    nonMovingSpace.prepare(true);
			    if (USE_CODE_SPACE) {
			        smallCodeSpace.prepare(true);
			    }
			    immortalSpace.prepare();
			    VM.memory.globalPrepareVMSpace();
				myMsSpace.prepare(true);
			}
			return;
		}
		
		if (phaseId == CLOSURE) {
			this.pilotTrace.prepare();
			addClosure();
			return;
		}
		
		if (phaseId == RELEASE) {
			pilotTrace.release();
			myMsSpace.release();
			super.collectionPhase(phaseId);
			return;
		}

		super.collectionPhase(phaseId);
	}

	private void startClosure() {
		if(TRACE_FLAVOR == DEPTH_FIX_DEPTH) {
			closure = CLOSURE_FIRST_SINGLE;
		} else if(TRACE_FLAVOR == TIME_FIX_TIME){
			closure = CLOSURE_FIRST_CONCURRENT;
		}
	}
	
	private void addClosure() {
		if(TRACE_FLAVOR == DEPTH_FIX_DEPTH) {
			if(closure == CLOSURE_FIRST_SINGLE ) {	
				closure = CLOSURE_SINGLE;
			} else if (closure == CLOSURE_SINGLE) {
				closure = CLOSURE_LAST_SINGLE;
			}
		} else if(TRACE_FLAVOR == TIME_FIX_TIME){
			if(closure == CLOSURE_FIRST_CONCURRENT) {
				closure = CLOSURE_SINGLE;
			} else if (closure == CLOSURE_SINGLE) {
				closure = CLOSURE_LAST_CONCURRENT;
			}
		}
	}
	
	static boolean isLastClosure() {
		if(closure == CLOSURE_LAST_SINGLE ||
				closure == CLOSURE_LAST_CONCURRENT) {
			return true;
		}
		return false;
	}
	
	static boolean isFirstClosure() {
		if(closure == CLOSURE_FIRST_SINGLE ||
				closure == CLOSURE_FIRST_CONCURRENT) {
			return true;
		}
		return false;
	}
	
	static boolean isConcurrentClosure() {
		if(closure == CLOSURE_FIRST_CONCURRENT ||
				closure == CLOSURE_LAST_CONCURRENT ||
				closure == CLOSURE_CONCURRENT) {
			return true;
		}
		return false;
	}
	/*****************************************************************************
	 * 
	 * Accounting
	 */

	/**
	 * Return the number of pages reserved for use given the pending allocation.
	 * The superclass accounts for its spaces, we just augment this with the
	 * mark-sweep space's contribution.
	 * 
	 * @return The number of pages reserved given the pending allocation,
	 *         excluding space reserved for copying.
	 */
	public int getPagesUsed() {
		return (myMsSpace.reservedPages() + super.getPagesUsed());
	}

	/**
	 * Calculate the number of pages a collection is required to free to satisfy
	 * outstanding allocation requests.
	 * 
	 * @return the number of pages a collection is required to free to satisfy
	 *         outstanding allocation requests.
	 */
	public int getPagesRequired() {
		return super.getPagesRequired() + myMsSpace.requiredPages();
	}

	/**
	 * @see org.mmtk.plan.Plan#willNeverMove
	 * 
	 * @param object
	 *            Object in question
	 * @return True if the object will never move
	 */
	@Override
	public boolean willNeverMove(ObjectReference object) {
		if (Space.isInSpace(MARK_SWEEP, object))
			return true;
		return super.willNeverMove(object);
	}

	/**
	 * Register specialized methods.
	 */
	@Interruptible
	protected void registerSpecializedMethods() {
		TransitiveClosure.registerSpecializedScan(SCAN_MARK,MetrixTraceLocal.class);
		super.registerSpecializedMethods();
	}

	/**
	 * Print something just before VM exit
	 */
	@Override
	@Interruptible
	public void notifyExit(int value) {
		super.notifyExit(value);
		Log.write(" === EXITING in total ");
		totalTime.printTotalMillis();
		Log.writeln(" ms");
		Log.flush();
		Stats.stopAll();
	}

	/**
	 * Print something before the cycle
	 */
	@Override
	public void printPreStats() {
		super.printPreStats();
		Log.write(" ===== BEFORE GC ===== \n");
		Log.flush();
	}

	/**
	 * Print something after VM boots
	 */
	@Override
	@Interruptible
	public void postBoot() {
		super.postBoot();
	    
	    Log.write("Running flavor: ");
		Log.write(TRACE_FLAVOR);
		short replacementPhaseID = -1;
		if(TRACE_FLAVOR == DEPTH_FIX_DEPTH) {
			replacementPhaseID = DFD_ClosurePhase;		
		} else if(TRACE_FLAVOR == TIME_FIX_TIME) {
			replacementPhaseID = TFT_ClosurePhase;	
		}
		if(replacementPhaseID != -1) {
			Log.write("; replacing the rootClosurePhase with ");
			Log.write(Phase.getName(replacementPhaseID));
			Log.writeln("");
			replacePhase(Phase.scheduleComplex(rootClosurePhase), Phase.scheduleComplex(replacementPhaseID));
		}
			
		Space.printVMMap();
	    VM.config.printConfig();
	    Stats.startAll();
	    
	    Log.write("\n ===== BOOTED ===== \n");
		Log.flush();
	}
}
